__author__ = "furoscame"
__date__ = "29.12.11 23:29"
__version__ = "Revision: 0.3.3"
debug = False
""" 
    This is the main of the OSA_Plugin
    It controls the data flow and handles special cases like
    leo goes offline, ...
"""

import time

import chessprogram_adapter
import leonardo_serial
import mosal_a
from mosal_message import MosalMessage

class OSAControl:
    """
    The Class that glue the communication
    """
    def __init__(self):
        """
            Open the communication to Leo and to chessprogram
        """
        self.print_version()
        self.osa_master = OSAControlState()
        self.leo_serial = leonardo_serial.LeoSerial()
        self.leo_serial.open_leo_com() # try to connect to leo

        # load current chessprogram configuration
        self.cp_adapter = chessprogram_adapter.ChessprogramAdapter(self)
        self.startup_ok = False

    pass

    def print_version(self):
        print ("This is Leonhart:" + __version__)
        print ("A serial OSA plugin to pimp old Leonardo board computers!")
        print ("Now trying to control Leonardo!")
        pass
    pass

    def start_up(self):
        """
            bring up chessprogram and leonardo
        """
        self.cp_adapter.start_program()
        i = 0
        while i < 60:
            ret = self.cp_adapter.is_running()
            #print ("stae", ret)
            if ret == 'ok':
                self.startup_ok = True
                break
            time.sleep(0.5)
            i += 1
            pass
        if self.startup_ok == False:
            print ("Troubles to start the chessprogramm!")
            print ("Check chessprogram configuration!")
            self.startup_ok = False
            return False

        if self.leo_serial.is_open() == True:
            self.leo_serial.set_receiver_proc(self.tell_to_engine)  # set data consumer
            self.leo_serial.shortcut() # go reading leo

            #synchronize CP to leo
            self.sync = True
            self.become_osa_master()
            return self.sync_leo_to_engine()
        else:
            print ("No connection to Leonardo!")
            self.startup_ok = False
            return False
        pass
    pass

    def become_osa_master_book(self):

        mosala = mosal_a.MosalA()
        mosal_str = mosala.get_data_area_cmd()  # retrieve resume data
        if debug == True:
            print (mosal_str)
        self.tell_to_leo(mosal_str)
        #time.sleep(1)

        # now we have correct resume data

        message = self.construct_searchmaster()
        mosal_str = mosala.get_mosal_string(message)
        if debug == True:
            print ("search master to leo", mosal_str)
        self.tell_to_leo(mosal_str)
        #time.sleep(1)

        # set level + clocks (search master is precondition)
        message = self.construct_new_level()
        mosal_str = mosala.get_mosal_string(message)
        if debug == True:
            print ("new level to leo", mosal_str)
        self.tell_to_leo(mosal_str)
        #time.sleep(1)

        # set search master according mode B/P
        if self.osa_master.mode == 'B':
            message = self.construct_listener()
            mosal_str = mosala.get_mosal_string(message)
            if debug == True:
                print ("listener to leo", mosal_str)
            self.tell_to_leo(mosal_str)
            #time.sleep(1)
            pass
        
        # evtl play according calc-state

        
        pass
    pass

    def become_osa_master(self):
        mosala = mosal_a.MosalA()
        mosal_str = mosala.get_data_area_cmd()  # retrieve resume data
        if debug == True:
            print ("become_osa_master:" ,mosal_str)
        self.tell_to_leo(mosal_str)
        #time.sleep(1)

        # now we have correct resume data
        message = self.construct_searchmaster()
        mosal_str = mosala.get_mosal_string(message)
        if debug == True:
            print ("search master to leo", mosal_str)
        self.tell_to_leo(mosal_str)

        # set level + clocks (search master is precondition)
        message = self.construct_new_level()
        mosal_str = mosala.get_mosal_string(message)
        if debug == True:
            print ("new level to leo", mosal_str)
        self.tell_to_leo(mosal_str)
        #time.sleep(1)
        pass
    pass

    def sync_leo_to_engine(self):
        """
            It provides that the cp get all information from leo to start the game
        """
        mosala = mosal_a.MosalA()
        while self.sync:
            mosal_str = mosala.get_level_cmd()
            if debug == True:
                print ("to leo", mosal_str)
            self.tell_to_leo(mosal_str)
            time.sleep(0.4) # hoping that leo sends the information via tell_to_engine

            mosal_str = mosala.get_position_cmd()
            if debug == True:
                print ("to leo", mosal_str)
            self.tell_to_leo(mosal_str)
            time.sleep(0.4)

            mosal_str = mosala.get_beep_cmd()
            if debug == True:
                print ("to leo", mosal_str)
            self.tell_to_leo(mosal_str)
            time.sleep(0.4)
            # succeed to sync
            if self.cp_adapter.sync == True:
                print ("sync done")
                self.beep_ready()
                if self.osa_master.calc_state == 1:
                    message = MosalMessage("BeginSearch")
                    mosal_str = mosala.get_mosal_string(message)
                return True
                break
            pass
        pass
    pass

    def tell_to_engine(self, mosal_str):
        """ 
            The central access to the engine.
            This called from the serial reader which delivers Mosal strings
        """
        if debug == True:
            print ("OSAControl:", mosal_str)
        self.read_flag = True
        mosala = mosal_a.MosalA()
        message = mosala.get_message_from_mosal_string(mosal_str)
        if debug == True:
            print (message.print_message())
        #handle resume
        if message.message == "Unkown":
            pass
            #self.cp_adapter.resend_move()
            #mosal_str = mosala.get_error_cmd()
            #self.tell_to_leo(mosal_str)
            #time.sleep(1)
        elif message.message == "DataArea":
            self.osa_master.set(message)
            if debug == True:
                self.osa_master.my_print()
            pass
        # handle stop
        elif message.message == "Stopping":
            message.message = "DataArea"
            message.set('data', self.osa_master.dump_data())
            mosal_str = mosala.get_mosal_string(message)
            if debug == True:
                print ("Data to leo:", mosal_str)
            self.tell_to_leo(mosal_str)
            time.sleep(1)

            #TODO downgrade level to save area lastlevel

            mosal_str = mosala.get_disconnect_cmd()
            if debug == True:
                print ("Data to leo:", mosal_str)
            self.tell_to_leo(mosal_str)
            time.sleep(1)
            self.terminate()
        #intercepting calculation from leo
        elif message.message == "Protocol":
            pass
        elif message.message == "InfoArea":
            if debug == True:
                message.message.print_message()
            if message.get('depth') > 0:
                self.osa_master.mode = 'P'
                # send abort search
                message = self.construct_abort_search()
                mosal_str = mosala.get_mosal_string(message)
                print ("Abort search to leo", mosal_str)
                self.tell_to_leo(mosal_str)
                time.sleep(1)
                # become search master
                message = self.construct_searchmaster()
                mosal_str = mosala.get_mosal_string(message)
                print ("Search master to leo", mosal_str)
                self.tell_to_leo(mosal_str)

                message = self.construct_new_level()
                mosal_str = mosala.get_mosal_string(message)
                print ("new level to leo", mosal_str)
                self.tell_to_leo(mosal_str)
                time.sleep(1)
                
                # check csa
                mosal_str = mosala.get_csa_cmd()
                print ("CSA from leo", mosal_str)
                self.tell_to_leo(mosal_str)
                time.sleep(10)
                # do sync leo cp
                if self.sync_leo_to_engine() == True:
                    self.cp_adapter.go()
                pass
        elif message.message == "Setlevel":
            if debug == True:
                print ("Got level")
            self.osa_master.curr_level = message.get('level')
            if self.osa_master.curr_level > 0x67:
                self.osa_master.mode = 'P'
            self.cp_adapter.tell_to_engine(message)
        elif  message.message == "Position":
            # sent in any case B/P
            self.cp_adapter.tell_to_engine(message)
        elif message.message == "Takeback":
            mosal_str = mosala.get_position_cmd()
            self.tell_to_leo(mosal_str) # consider state in cp_adapter
        else:
            self.cp_adapter.tell_to_engine(message)
            pass
    pass

    def tell_to_leo(self, mosal_string):
        """
            The central access to leo
            all communication should be done here
        """
        time.sleep(0.5)
        self.leo_serial.write(mosal_string + "\r\n")
        #self.leo_serial.write(mosal_string + "\r\n" + "}"+ "\r")
        #time.sleep(0.1)

    pass

    def run(self):
        """
            Mostly waiting ready to react to special circumstances
            like leo abandon or else
        """
        i = 0
        while True:
            #TODO: all reactions
            if self.read_flag == False:
                i += 1
            else:
                self.read_flag = False
                i = 0
            if i == 10:
                #mosala = mosal_a.MosalA()
                #mosal_str  = mosala.get_csa_cmd()
                #print (mosal_str)
                self.cp_adapter.resend_move()
                #self.leo_serial.write( "%"+ "\n")
                #import re
                #self.leo_serial.write(mosal_str+ "\n")
            time.sleep(2)
            pass
        pass
    pass

    def terminate(self):
        self.leo_serial.destroy_serial()
        self.cp_adapter.terminate()
        pass
    pass

    def construct_new_level(self):
        message = MosalMessage("Setlevel")
        message.set('level', 0x00) #self.osa_master.curr_level
        message.set('wtime', self.osa_master.wtime)
        message.set('btime', self.osa_master.btime)
        message.set('movetime', 0)
        message.set('max_level', 0x77)
        message.set('int_level', 0x77) #self.osa_master.curr_level
        return message
    pass

    def construct_searchmaster(self):
        message = MosalMessage("CSA") # 0807118001B507C4
        message.set('serial_search_master', '05')  # 0000 0100 search mster only
        #message.serial_search_master = '10'  # 0000 0100 search mster only
        #message.serial_search_master = '0C' # 0000 0101 serch master an second count
        if debug == True:
            print ("construct_searchmaster: ", message.print_message())
        return message
        pass

    def construct_abort_search(self):
        message = MosalMessage("AbortSearch")
        return message
        pass
    pass

    def construct_listener_(self):
        message = MosalMessage("CSA") # 0807118001B507C4
        message.set('serial_search_master', '11')  # B5 1011 0101
        if debug == True:
            print (message.print_message())
        return message
        pass
    pass

    def beep_ready(self):
        mosala = mosal_a.MosalA()
        message = MosalMessage("Beep")
        message.set('duration', 48)
        message.set('frequency', 96)
        mosal_str = mosala.get_mosal_string(message)
        self.tell_to_leo(mosal_str)
        #time.sleep(0.4)
        message.set('duration', 48)
        message.set('frequency', 48)
        mosal_str = mosala.get_mosal_string(message)
        self.tell_to_leo(mosal_str)
        #time.sleep(0.4)
        message.set('duration', 48)
        message.set('frequency', 96)
        mosal_str = mosala.get_mosal_string(message)
        self.tell_to_leo(mosal_str)
        #time.sleep(0.4)
        message.set('duration', 48)
        message.set('frequency', 48)
        mosal_str = mosala.get_mosal_string(message)
        self.tell_to_leo(mosal_str)
        pass
    pass

pass

class OSAControlState:
    """
        This clas reflects the overall states on OSA-Plugin
    """
    def __init__(self):


        self.mode = ' '  # Bookmode|Playmode Just for the reason of simple access here
        self.set_default()
        pass

    def calc_checksum(self):
        """
            Quite simple checksum, but hopefully sufficient for that purpose
        """
        self.check_byte = (self.prefix + self.curr_level + self.last_level + self.CSA + self.calc_state + self.wtime + self.btime) % 0x100
    pass

    def check_data(self):
        if self.check_byte == (self.prefix + self.curr_level + self.last_level + self.CSA + self.calc_state + self.wtime + self.btime) % 0x100:
            print ("Checksum OK")
        else:
            print ("Checksum error")
            self.set_default()

        pass
    pass

    def set_default(self):
        self.prefix = 0x72
        self.curr_level = 0x00
        self.last_level = 0x67
        self.CSA = 0x01
        self.calc_state = 0
        self.wtime = 0x0000
        self.btime = 0x0000
        self.calc_checksum()

        if self.CSA == '91':
            self.mode = 'P'
        else:
            self.mode = 'B'
        pass
    pass

    def set(self, message):

        self.prefix = int(message.get('data')[0:0 + 2], 16)
        self.curr_level = int(message.get('data')[2:2 + 2], 16)
        self.last_level = int(message.get('data')[4:4 + 2], 16)
        self.CSA = int(message.get('data')[6:6 + 2], 16)
        self.calc_state = int(message.get('data')[8:8 + 2], 16)
        self.wtime = int(message.get('data')[10:10 + 4], 16)
        self.btime = int(message.get('data')[14:14 + 4], 16)
        self.check_byte = int(message.get('data')[18:18 + 2], 16)
        self.check_data()
        #self.calc_checksum()

        if self.CSA == '91':
            self.mode = 'P'
        else:
            self.mode = 'B'
        pass
    pass

    def dump_data(self):
        self.calc_checksum()
        ret = '{:02X}'.format(self.prefix)   + '{:02X}'.format(self.curr_level) + '{:02X}'.format(self.last_level) + '{:02X}'.format(self.CSA) \
            + '{:02X}'.format(self.calc_state) + '{:04X}'.format(self.wtime)      + '{:04X}'.format(self.btime)      + '{:02X}'.format(self.check_byte)
        return ret
    pass

    def my_print(self):
        print ("prefix: ", self.prefix)
        print ("curr_level: ", self.curr_level)
        print ("last level: ", self.last_level)
        print ("CSA: ", self.CSA)
        print ("calc state: ", self.calc_state)
        print ("wtime: ", self.wtime)
        print ("btime: ", self.btime)
        print ("CRC: ", self.check_byte)
        pass
    pass
pass

if __name__ == "__main__":

    osa_control = OSAControl()
    if osa_control.start_up():
        osa_control.run()
        exit(0)
    else:
        print ("Terminate program!")
        osa_control.terminate()
        raw_input("Press return!")
        exit(-1)
